/*
 *     Prionace - A file navigator to manage your files
 *
 *     Copyright (C) 2012 Francivan Bezerra
 *
 *     This file is part of Prionace.
 *
 *     Prionace is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     Prionace is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with Prionace.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.fba.prionace.ui.viewer;

import java.awt.Cursor;
import java.awt.Point;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.AccessControlException;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Vector;
import java.util.regex.PatternSyntaxException;

import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.TransferHandler;
import javax.swing.filechooser.FileSystemView;

import org.fba.prionace.file.FileTransferHandler;
import org.fba.prionace.file.JFile;
import org.fba.prionace.process.ExecChooser;
import org.fba.prionace.process.ExecProcess;
import org.fba.prionace.ui.ExceptionHandler;
import org.fba.prionace.ui.FilesPane;

public abstract class Viewer extends JScrollPane implements MouseListener,
		MouseMotionListener {

	protected FilesPane owner;

	protected File currentPath;

	protected Vector<JFile> files;

	protected ArrayDeque<File> stackPrevious;

	protected ArrayDeque<File> stackNext;

	protected boolean hiddenFilesVisible;

	protected int totalFiles;

	protected int totalDirs;

	protected Point mousePressedCoordinates;

	public Viewer() {
		super();
		currentPath = null;
		files = new Vector<JFile>();
		stackPrevious = new ArrayDeque<File>();
		stackNext = new ArrayDeque<File>();
		hiddenFilesVisible = false;
		mousePressedCoordinates = new Point();
		getVerticalScrollBar().setUnitIncrement(14);
		getHorizontalScrollBar().setUnitIncrement(14);
		setTransferHandler(new FileTransferHandler());
	}

	public Viewer(File currentPath) throws FileNotFoundException {
		this();
		setCurrentPath(currentPath);
	}

	public Viewer(String currentPath) throws FileNotFoundException {
		this(new File(currentPath));
	}

	public FilesPane getOwner() {
		return owner;
	}

	public void setOwner(FilesPane owner) {
		this.owner = owner;
	}

	public String getCurrentDirName() {
		return currentPath.getName().isEmpty() ? currentPath.getAbsolutePath()
				: currentPath.getName();
	}

	public File getCurrentPath() {
		return currentPath;
	}

	public void setCurrentPath(String currentPath) throws FileNotFoundException {
		setCurrentPath(new File(currentPath));
	}

	public void setCurrentPath(File currentPath) throws FileNotFoundException {
		stackPrevious.offerFirst(currentPath);
		stackNext.clear();
		changePath(currentPath);
	}

	public void movePrevious() throws FileNotFoundException {
		File previous;
		if (stackPrevious.size() > 1) {
			previous = stackPrevious.pollFirst();
			stackNext.offerFirst(previous);
			changePath(stackPrevious.getFirst());
		}
	}

	public void moveNext() throws FileNotFoundException {
		File next = stackNext.pollFirst();
		if (next != null) {
			stackPrevious.offerFirst(next);
			changePath(next);
		}
	}

	private void changePath(File path) throws FileNotFoundException {
		if (!path.exists()) {
			throw new FileNotFoundException("O diretório informado não existe");
		}

		if (path.isFile()) {
			throw new FileNotFoundException(
					"O caminho informado não é diretório");
		}

		if (!path.canExecute()) {
			throw new AccessControlException("Acesso negado");
		}

		TransitionEffect effect = new TransitionEffect(getViewport());
		int type = TransitionEffect.NONE;

		if (getCurrentPath() != null
				&& !getCurrentPath().getAbsolutePath().equals(
						path.getAbsolutePath())) {
			if (path.getAbsolutePath().contains(
					getCurrentPath().getAbsolutePath())) {
				type = TransitionEffect.ZOOM_IN;
			} else if (getCurrentPath().getAbsolutePath().contains(
					path.getAbsolutePath())) {
				type = TransitionEffect.ZOOM_OUT;
			} else {
				type = TransitionEffect.SLIDE;
			}
		}
		effect.doEffect(type, TransitionEffect.DISAPPEAR);

		this.currentPath = path;
		refreshFilesList();

		effect.doEffect(type, TransitionEffect.APPEAR);
	}

	public Vector<JFile> getFiles() {
		return files;
	}

	public boolean isHiddenFilesVisible() {
		return hiddenFilesVisible;
	}

	public void setHiddenFilesVisible(boolean hiddenFilesVisible) {
		this.hiddenFilesVisible = hiddenFilesVisible;
	}

	public int getTotalFiles() {
		return totalFiles;
	}

	public int getTotalDirs() {
		return totalDirs;
	}

	public void selectAll() {
		for (JFile file : files) {
			file.setSelected(true);
		}
	}

	public void selectNone() {
		for (JFile file : files) {
			file.setSelected(false);
		}
	}

	public void selectRegex(String regex) {
		selectNone();

		try {
			for (JFile file : files) {
				if (file.getText().matches(regex)) {
					file.setSelected(true);
				}
			}
		} catch (PatternSyntaxException e) {
			ExceptionHandler.showException(e, this);
		}
	}

	public void invertSelection() {
		for (JFile file : files) {
			file.setSelected(!file.isSelected());
		}
	}

	public JFile getFirstSelected() {
		for (JFile file : files) {
			if (file.isSelected()) {
				return file;
			}
		}
		return null;
	}

	public Vector<JFile> getSelected() {
		Vector<JFile> selected = new Vector<JFile>();

		for (JFile file : files) {
			if (file.isSelected()) {
				JFile selection = new JFile(file.getFile());
				selected.add(selection);
			}
		}

		return selected;
	}

	public Vector<File> getSelectedFiles() {
		Vector<File> selectedFiles = new Vector<File>();
		Vector<JFile> selected = getSelected();

		for (JFile file : selected) {
			selectedFiles.add(file.getFile());
		}

		return selectedFiles;
	}

	public void deleteSelected() {
		Vector<JFile> files = getSelected();

		if (files.size() > 0) {
			for (JFile file : files) {
				file.delete();
			}
			refreshFilesList();
		}
	}

	public void refreshFilesList() {
		if (currentPath != null) {

			for (JFile file : files) {
				if (file.getIcon() != null) {
					file.getIcon().getImage().flush();
				}
			}

			files.removeAllElements();
			totalFiles = 0;
			totalDirs = 0;

			for (File file : currentPath.listFiles()) {
				if (file.isHidden()) {
					if (hiddenFilesVisible) {
						addFileToList(file);
					}
				} else {
					addFileToList(file);
				}
			}

			Collections.sort(files);
		}
	}

	public void showRoots() {
		File[] roots = File.listRoots();
		FileSystemView fsView = FileSystemView.getFileSystemView();

		currentPath = null;

		for (JFile file : files) {
			if (file.getIcon() != null) {
				file.getIcon().getImage().flush();
			}
		}

		files.removeAllElements();
		totalFiles = 0;
		totalDirs = 0;

		for (File root : roots) {
			if (!fsView.getSystemDisplayName(root).isEmpty()) {
				addFileToList(root);
			}
		}

		for (JFile file : files) {
			file.setText(fsView.getSystemDisplayName(file.getFile()));
			file.setIcon(fsView.getSystemIcon(file.getFile()));
		}
	}

	protected void addFileToList(File file) {
		JFile jfile = new JFile(file);
		jfile.addMouseListener(this);
		jfile.addMouseMotionListener(this);
		files.add(jfile);

		if (file.isFile()) {
			totalFiles++;
		} else {
			totalDirs++;
		}
	}

	public void mouseClicked(MouseEvent e) {
		JFile clicked = (JFile) e.getSource();

		if (e.getButton() == MouseEvent.BUTTON1) {
			if (e.getClickCount() == 2) {
				if (clicked.getFile().isDirectory()) {
					try {
						setCursor(Cursor
								.getPredefinedCursor(Cursor.WAIT_CURSOR));
						setCurrentPath(clicked.getFile());
						owner.refreshBars();
					} catch (Exception ex) {
						ExceptionHandler.showException(ex, this);
					} finally {
						setCursor(null);
					}
				} else {
					ExecProcess execProcess = null;
					try {
						execProcess = ExecProcess.getInstance();
						execProcess.open(clicked.getFile());
					} catch (FileNotFoundException ex) {
						ExecChooser chooser = new ExecChooser();
						chooser.setMimeType(clicked.getMimeType());
						int option = JOptionPane.showConfirmDialog(this,
								chooser, "Abrir com",
								JOptionPane.OK_CANCEL_OPTION,
								JOptionPane.PLAIN_MESSAGE);

						if (option == JOptionPane.OK_OPTION) {
							try {
								execProcess.addExecProcess(clicked.getFile(),
										chooser.getCommand());
								execProcess.open(clicked.getFile());
							} catch (IOException exc) {
								ExceptionHandler.showException(exc, this);
							}
						}
					} catch (IOException ex) {
						ExceptionHandler.showException(ex, this);
					}
				}
			}
		} else if (e.getButton() == MouseEvent.BUTTON2
				&& clicked.getFile().isDirectory()) {
			try {
				// TODO pegar o viewer de um viewer factory
				setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
				owner.addViewer(new IconsViewer(clicked.getFile()));
			} catch (FileNotFoundException ex) {
				ExceptionHandler.showException(ex, this);
			} finally {
				setCursor(null);
			}
		}
	}

	public void mouseEntered(MouseEvent e) {
	}

	public void mouseExited(MouseEvent e) {
	}

	public void mousePressed(MouseEvent e) {
		mousePressedCoordinates.x = e.getX();
		mousePressedCoordinates.y = e.getY();
	}

	public void mouseReleased(MouseEvent e) {
		if ((e.getModifiersEx() & InputEvent.SHIFT_DOWN_MASK) == InputEvent.SHIFT_DOWN_MASK) {
			int index = 0;
			JFile file = files.elementAt(index);

			while (index < files.size() && !file.isSelected()
					&& file != e.getSource()) {
				file = files.elementAt(++index);
			}

			if (++index < files.size()) {
				file = files.elementAt(index);
			}

			while (index < files.size() && !file.isSelected()
					&& file != e.getSource()) {
				file.setSelected(true);
				file = files.elementAt(++index);
			}

			while (++index < files.size()) {
				file = files.elementAt(index);
				file.setSelected(false);
			}
		} else if ((e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) != MouseEvent.CTRL_DOWN_MASK) {
			for (JFile file : files) {
				if (file != e.getSource()) {
					file.setSelected(false);
				} else {
					file.setSelected(true);
				}
			}
		}
	}

	public void mouseDragged(MouseEvent e) {
		int diffX = Math.abs(e.getX() - mousePressedCoordinates.x);
		int diffY = Math.abs(e.getY() - mousePressedCoordinates.y);
		int action = (e.getModifiersEx() & InputEvent.CTRL_DOWN_MASK) == InputEvent.CTRL_DOWN_MASK ? TransferHandler.COPY
				: TransferHandler.MOVE;

		if (diffX > 10 || diffY > 10) {
			TransferHandler handler = getTransferHandler();
			handler.exportAsDrag(this, e, action);
		}
	}

	public void mouseMoved(MouseEvent e) {
	}
}
